package cron

import (
	"context"
	"strings"
	"time"

	"code.bydev.io/cht/customer/kyc_admin_service/internal/constant"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/domain/entity"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/model"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/constdata"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/helper"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/kycprovider"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/pkg/kycprovider/dto"
	"code.bydev.io/cht/customer/kyc_admin_service/internal/utils"
	"code.bydev.io/frameworks/byone/core/logc"
	"code.bydev.io/frameworks/byone/core/logx"
	"code.bydev.io/frameworks/byone/core/trace"
	"emperror.dev/errors"
	jsoniter "github.com/json-iterator/go"
	"github.com/samber/lo"
	"go.opentelemetry.io/otel"
)

func (d *Domain) GetKYCApprovedListByStartID(ctx context.Context, startID int64, limit int) ([]*entity.KYCCommon, error) {
	ctx, span := otel.Tracer(trace.TracerName).Start(ctx, utils.GetPFuncName())
	defer span.End()

	list, err := d.kycRecordCommon.GetKYCApprovedListByStartID(ctx, startID, limit)
	if err != nil {
		logc.Errorw(ctx, "s.GetKYCApprovedListByStartID error", logc.Field("err", err), logc.Field("startID", startID))
		return nil, err
	}

	return kycRecordCommon2KYCCommon(list), nil
}

func kycRecordCommon2KYCCommon(list []*model.KycRecordCommon) (ret []*entity.KYCCommon) {
	if len(list) == 0 {
		return nil
	}

	lo.ForEach(list, func(record *model.KycRecordCommon, i int) {
		ret = append(ret, &entity.KYCCommon{
			ID:            record.ID,
			MemberID:      record.MemberID,
			ApplicantID:   record.ApplicantID,
			DataID:        record.DataID,
			Provider:      constant.KYCProvider(record.Provider),
			Country:       record.Country,
			Level:         constant.KYCLevel(record.Level),
			LevelName:     record.LevelName,
			Type:          record.Type,
			State:         constant.KYCState(record.State),
			Result:        constant.KYCDecision(record.Result),
			RejectLabels:  strings.Split(record.RejectLabels, ","),
			CanReprocess:  record.CanReprocess,
			Residence:     record.Residence,
			Nationality:   record.Nationality,
			Passed:        record.Passed,
			SelectDocType: constant.KYCDocType(record.SelectDocType),
		})
	})

	return
}

func (d *Domain) GetLatestApprovedOnboardPOIRecord(ctx context.Context, memberID int64) (*entity.KYCAction, error) {
	ctx, span := otel.Tracer(trace.TracerName).Start(ctx, utils.GetPFuncName())
	defer span.End()

	action, err := d.kycActionState.GetLatestApprovedOnboardPOIRecord(ctx, memberID)
	if err != nil {
		logc.Errorw(ctx, "s.kycActionState.GetLatestApprovedOnboardPOIRecord error", logc.Field("err", err), logc.Field("memberID", memberID))
		return nil, err
	}

	return d.kycActionState2KYCAction(ctx, action), nil
}

func (d *Domain) kycActionState2KYCAction(ctx context.Context, actionState *model.KycActionState) (ret *entity.KYCAction) {
	if actionState == nil {
		return nil
	}
	var err error

	ret = &entity.KYCAction{
		ID:                   actionState.ID,
		MemberID:             actionState.MemberID,
		SiteID:               actionState.SiteID,
		ExternalUID:          actionState.ExternalUID,
		RecordID:             actionState.RecordID,
		AccountID:            actionState.AccountID,
		VerifyContent:        actionState.VerifyContent,
		Level:                constant.KYCLevel(actionState.Level),
		Channel:              constant.KYCProvider(actionState.Channel),
		Action:               constant.KYCAction(actionState.Action),
		Decision:             constant.KYCDecision(actionState.Decision),
		ProviderDecision:     constant.KYCDecision(actionState.ProviderDecision),
		RejectReason:         actionState.RejectReason,
		ProviderRejectReason: actionState.ProviderRejectReason,
		DocType:              constant.KYCDocType(actionState.DocType),
		DocSubtype:           actionState.DocSubtype,
		DocNumber:            actionState.DocNumber,
		Country:              actionState.Country,
		Nationality:          actionState.Nationality,
		AddressCountry:       actionState.AddressCountry,
		FirstName:            actionState.FirstName,
		LastName:             actionState.LastName,
		Dob:                  actionState.Dob,
		IdempotentID:         actionState.IdempotentID,
		ReviewTime:           actionState.ReviewTime,
		ProviderCreateAt:     actionState.ProviderCreateAt,
		CreatedAt:            actionState.CreatedAt,
		UpdatedAt:            actionState.UpdatedAt,
		DocDetail:            &entity.KYCActionDocDetail{},
		IssuingDate:          actionState.IssuingDate,
		ExpireDate:           actionState.ExpireDate,
		FaceDetail:           &entity.KYCActionFaceDetail{},
		AddressDetail:        &entity.KYCActionAddressDetail{},
		ClientDetail:         &entity.KYCActionClientDetail{},
		SerialType:           constant.KYCVerificationType(actionState.SerialType),
		IsTransfer:           constant.KYCTransfer(actionState.IsTransfer),
		SelectDocType:        constant.KYCDocType(actionState.SelectDocType),
		SelectCountry:        actionState.SelectCountry,
		BusinessID:           constant.BusinessID(actionState.BusinessID),
	}

	// 证件图片信息
	actionState.DocDetail, err = helper.DeAES256CodeString(d.secHub, actionState.DocDetail)
	if err != nil {
		logc.Errorw(ctx, "helper.DeAES256CodeString error", logc.Field("err", err), logc.Field("DocDetail", actionState.DocDetail))
	}

	if actionState.DocDetail != "" {
		logc.Debugw(ctx, "actionState.DocDetail", logc.Field("DocDetail", actionState.DocDetail))
		docDetail := new(entity.KYCActionDocDetail)
		err = jsoniter.UnmarshalFromString(actionState.DocDetail, docDetail)
		if err != nil {
			logc.Errorw(ctx, "jsoniter.UnmarshalFromString error", logc.Field("err", err), logc.Field("DocDetail", actionState.DocDetail))
			return
		}
		logc.Debugw(ctx, "docDetail", logc.Field("docDetail", docDetail))
		if docDetail != nil && docDetail.DocFront != "" {
			ret.DocImgFrontS3Info, err = convert2ImgS3Info(ctx, docDetail.DocFront)
			if err != nil {
				logc.Errorw(ctx, "convert2ImgS3Info error", logc.Field("err", err), logc.Field("DocFront", docDetail.DocFront))
				return
			}
		}
		if docDetail != nil && docDetail.DocBack != "" {
			ret.DocImgBackS3Info, err = convert2ImgS3Info(ctx, docDetail.DocBack)
			if err != nil {
				logc.Errorw(ctx, "convert2ImgS3Info error", logc.Field("err", err), logc.Field("DocBack", docDetail.DocBack))
				return
			}
		}
	}

	// 人脸信息
	actionState.FaceDetail, err = helper.DeAES256CodeString(d.secHub, actionState.FaceDetail)
	if err != nil {
		logc.Errorw(ctx, "helper.DeAES256CodeString error", logc.Field("err", err), logc.Field("FaceDetail", actionState.FaceDetail))
		return
	}
	if actionState.FaceDetail != "" {
		faceDetail := new(entity.KYCActionFaceDetail)
		err = jsoniter.UnmarshalFromString(actionState.FaceDetail, faceDetail)
		if err != nil {
			logc.Errorw(ctx, "jsoniter.UnmarshalFromString error", logc.Field("err", err), logc.Field("FaceDetail", actionState.FaceDetail))
			return
		}
		if faceDetail != nil && faceDetail.FaceImage != "" {
			logc.Debugw(ctx, "faceDetail", logc.Field("faceDetail", faceDetail))
			ret.DocImgSelfieS3Info, err = convert2ImgS3Info(ctx, faceDetail.FaceImage)
			if err != nil {
				logc.Errorw(ctx, "convert2ImgS3Info error", logc.Field("err", err), logc.Field("FaceImage", faceDetail.FaceImage))
				return
			}
		}
	}

	return
}

func convert2ImgS3Info(ctx context.Context, imgS3InfoStr string) (imgS3Info *entity.KYCImageS3Info, err error) {
	imgS3Info = new(entity.KYCImageS3Info)
	err = jsoniter.UnmarshalFromString(imgS3InfoStr, imgS3Info)
	if err != nil {
		logc.Errorw(ctx, "jsoniter.UnmarshalFromString error", logc.Field("err", err), logc.Field("imgS3InfoStr", imgS3InfoStr))
		return nil, err
	}
	return
}

// GetListByIdempotentID get list by idempotent_id
func (d *Domain) GetListByIdempotentID(ctx context.Context, idempotentID string) ([]*entity.KYCImage, error) {
	ctx, span := otel.Tracer(trace.TracerName).Start(ctx, utils.GetPFuncName())
	defer span.End()

	list, err := d.kycActionImage.GetListByIdempotentID(ctx, idempotentID)
	if err != nil {
		logc.Errorw(ctx, "s.kycActionImage.GetListByIdempotentID error", logc.Field("err", err), logc.Field("idempotentID", idempotentID))
		return nil, err
	}

	return kycActionImage2KYCImage(list), nil
}

// kycActionImage2KYCImage
func kycActionImage2KYCImage(list []*model.KycActionImage) (ret []*entity.KYCImage) {
	if len(list) == 0 {
		return nil
	}

	lo.ForEach(list, func(image *model.KycActionImage, i int) {
		ret = append(ret, &entity.KYCImage{
			ID:           image.ID,
			MemberID:     image.MemberID,
			Provider:     constant.KYCProvider(image.Provider),
			IdempotentID: image.IdempotentID,
			AccountID:    image.AccountID,
			RecordID:     image.RecordID,
			ImgID:        image.ImgID,
			ImgType:      constant.KYCImgType(image.ImgType),
			ImgStatus:    constant.KYCImgStatus(image.ImgStatus),
			RetryTimes:   image.RetryTimes,
		})
	})

	return
}

// GetCountByIdempotentID returns the count of the given id in the given database
func (d *Domain) GetCountByIdempotentID(ctx context.Context, idempotentID string) (int64, error) {
	ctx, span := otel.Tracer(trace.TracerName).Start(ctx, utils.GetPFuncName())
	defer span.End()

	count, err := d.kycActionImage.GetCountByIdempotentID(ctx, idempotentID)
	if err != nil {
		logc.Errorw(ctx, "s.kycActionImage.GetCountByIdempotentID error", logc.Field("err", err), logc.Field("idempotentID", idempotentID))
		return 0, err
	}

	return count, nil
}

func (d *Domain) SelfieCompensate(ctx context.Context, limit int) error {
	startID, err := d.GetScannerStartID(ctx, KeySelfieCompensateScanner)
	if err != nil {
		logc.Errorw(ctx, "s.GetScanStartID error", logc.Field("err", err))
		return err
	}
	ctx = logx.ContextWithFields(ctx, logc.Field("startID", startID), logc.Field("action", "SelfieCompensate"))
	logc.Infow(ctx, "GetScanStartID begin")
	for {
		list, err := d.GetKYCApprovedListByStartID(ctx, startID, limit)
		if err != nil {
			logc.Errorw(ctx, "s.GetListByProviderAndID error", logc.Field("err", err), logc.Field("startID", startID))
			return err
		}
		if len(list) == 0 {
			logc.Infow(ctx, "no list")
			return nil
		}
		lo.ForEach(list, func(com *entity.KYCCommon, i int) {
			startID = int64(com.ID)
			_ = d.SetScannerStartID(ctx, KeySelfieCompensateScanner, startID)
			logc.Infow(ctx, "Start to handle", logc.Field("ID", startID))

			// 判断是否是 jumio
			if com.Provider == constant.ProviderJumio {
				logc.Infow(ctx, "SelfieCompensateSkipJumio", logc.Field("ID", startID), logc.Field("memberID", com.MemberID))
				return
			}
			// 获取最后一次 Onaboard POI 记录
			action, err := d.GetLatestApprovedOnboardPOIRecord(ctx, com.MemberID)
			if err != nil {
				logc.Errorw(ctx, "s.GetLatestApprovedOnboardPOIRecord error", logc.Field("err", err), logc.Field("memberID", com.MemberID))
				return
			}
			if action == nil {
				logc.Warnw(ctx, "SelfieCompensateLostActionState", logc.Field("memberID", com.MemberID))
				return
			}

			// 查询是否缺少人脸
			if action.DocImgSelfieS3Info == nil {
				// 再次检查 kyc_action_image
				count, err := d.GetCountByIdempotentID(ctx, action.IdempotentID)
				if err != nil {
					logc.Errorw(ctx, "s.GetCountByIdempotentID error", logc.Field("err", err), logc.Field("idempotentID", action.IdempotentID))
					return
				}
				if count > 0 {
					logc.Infow(ctx, "SelfieCompensateSkip", logc.Field("ID", startID), logc.Field("memberID", com.MemberID))
					return
				}
				err = d.CompensateOnboardingPOI(ctx, com, action)
				if err != nil {
					logc.Errorw(ctx, "s.CompensateOnboardingPOI error", logc.Field("err", err), logc.Field("memberID", com.MemberID))
				} else {
					logc.Infow(ctx, "SelfieCompensateSuccess", logc.Field("ID", startID), logc.Field("memberID", com.MemberID))
				}
				return
			}
		})
		time.Sleep(20 * time.Microsecond)
	}
}

func (d *Domain) CompensateOnboardingPOI(ctx context.Context, kycCommon *entity.KYCCommon, kycAction *entity.KYCAction) error {
	// 检查是否有人脸
	provider := kycprovider.NewProvider(d.logger, dto.KYCProvider(kycCommon.Provider), d.teller)
	actionImages, err := provider.GetKYCOnboardingPOIActionImages(ctx, kycCommon.MemberID, kycAction.AccountID, kycAction.RecordID, kycAction.IdempotentID)
	if err != nil {
		if errors.Is(err, constdata.ApplicantInvalidOrNotFound) {
			logc.Warnw(ctx, "SelfieCompensateSkipApplicantNotFound", logc.Field("memberID", kycCommon.MemberID))
			return nil
		}
		logc.Errorw(ctx, "provider.GetKYCOnboardingPOIActionImages error", logc.Field("err", err), logc.Field("memberID", kycCommon.MemberID))
		return err
	}
	if len(actionImages) == 0 {
		logc.Warnw(ctx, "SelfieCompensateLostActionImages", logc.Field("memberID", kycCommon.MemberID))
		return nil
	}

	return d.adminRepo.CreateActionImages(ctx, nil, actionImages)
}

func (d *Domain) GetAllApprovedSupplementPOIRecords(ctx context.Context, memberID int64) ([]*entity.KYCAction, error) {
	ctx, span := otel.Tracer(trace.TracerName).Start(ctx, utils.GetPFuncName())
	defer span.End()

	actions, err := d.kycActionState.GetAllApprovedSupplementPOIRecord(ctx, memberID)
	if err != nil {
		logc.Errorw(ctx, "s.kycActionState.GetAllApprovedSupplementPOIRecords error", logc.Field("err", err), logc.Field("memberID", memberID))
		return nil, err
	}

	res := make([]*entity.KYCAction, 0)
	for _, v := range actions {
		res = append(res, d.kycActionState2KYCAction(ctx, v))
	}

	return res, nil
}
